Skip to content

[Remove Vuetify from Studio] 'Create an account' page#5701

Open
vtushar06 wants to merge 9 commits intolearningequality:unstablefrom
vtushar06:remove-vuetify-5670
Open

[Remove Vuetify from Studio] 'Create an account' page#5701
vtushar06 wants to merge 9 commits intolearningequality:unstablefrom
vtushar06:remove-vuetify-5670

Conversation

@vtushar06
Copy link
Contributor

@vtushar06 vtushar06 commented Feb 13, 2026

Summary

Migrates the Create an account page from Vuetify to Kolibri Design System (KDS) as part of issue #5670.

Changes:

  1. ReplacedVForm, VSelect, VSlideYTransition, VLayout, VInput with KDS equivalents (KSelect, KTransition, KCheckbox, KTextbox, KButton, KRouterLink)
  2. Replaced shared Vuetify wrappers (TextField, EmailField, PasswordField, TextArea, Checkbox, Banner, ImmersiveModalLayout) with KDS components and two new wrapper components:
  • StudioEmailField.vue — wraps KTextbox with email input handling and error display

  • StudioPasswordField.vue — wraps KTextbox with password type and error display

  1. Replaced Vuetify form validation ($refs.form.validate() + computed rule arrays) with a custom validateField() / validateForm() approach that:
  • Validates each text field independently on blur (errors accumulate across fields)
  • Clears field errors on input
  • Validates all fields on submit
  1. Replaced ActionLinkpolicy links with KRouterLink

ScreenRecording:

Remove-vuetify-5670-2.mp4
Remove-vuetify-5670.mp4

References

Closes #5670

Reviewer guidance

Login as a@a.com with password a
Go to Create Page

Copilot AI review requested due to automatic review settings February 13, 2026 17:05
@learning-equality-bot
Copy link

👋 Thanks for contributing!

We will assign a reviewer within the next two weeks. In the meantime, please ensure that:

  • You ran pre-commit locally
  • All issue requirements are satisfied
  • The contribution is aligned with our Contributing guidelines. Pay extra attention to Using generative AI. Pull requests that don't follow the guidelines will be closed.

We'll be in touch! 😊

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request migrates the "Create an account" page from Vuetify to the Kolibri Design System (KDS) as part of the ongoing effort to remove Vuetify dependencies from Studio (issue #5060). The migration replaces Vuetify form components with KDS equivalents and implements custom form validation logic to replace Vuetify's built-in validation system.

Changes:

  • Replaced Vuetify components (VForm, VSelect, VSlideYTransition, VLayout, VInput) with KDS components (KSelect, KTransition, KCheckbox, KTextbox, KButton, KRouterLink)
  • Created two new wrapper components StudioEmailField and StudioPasswordField for consistent email and password input handling
  • Implemented custom form validation with validateField() and validateForm() methods to replace Vuetify's validation system

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
contentcuration/contentcuration/frontend/accounts/pages/Create.vue Main migration: replaced Vuetify components with KDS, implemented custom validation, updated styling
contentcuration/contentcuration/frontend/accounts/components/form/StudioEmailField.vue New wrapper component for email input using KTextbox with validation and error display
contentcuration/contentcuration/frontend/accounts/components/form/StudioPasswordField.vue New wrapper component for password input using KTextbox with error display
Comments suppressed due to low confidence (1)

contentcuration/contentcuration/frontend/accounts/components/form/StudioPasswordField.vue:50

  • According to the acceptance criteria in issue #5670, if there is no unit test suite, a new one should be created using @testing-library/vue. No unit tests have been added for the Create page or the new StudioEmailField and StudioPasswordField components.

Consider adding unit tests for the new components and the updated Create page to verify the form validation logic, error handling, and user interactions work as expected.

<template>

  <KTextbox
    :value="value"
    type="password"
    :label="label || $tr('passwordLabel')"
    :invalid="hasError"
    :invalidText="errorText"
    :showInvalidText="hasError"
    @input="$emit('input', $event)"
    @blur="$emit('blur')"
  />

</template>


<script>

  export default {
    name: 'StudioPasswordField',
    props: {
      value: {
        type: String,
        default: '',
      },
      label: {
        type: String,
        default: null,
      },
      errorMessages: {
        type: Array,
        default: () => [],
      },
    },
    computed: {
      hasError() {
        return this.errorMessages && this.errorMessages.length > 0;
      },
      errorText() {
        return this.hasError ? this.errorMessages[0] : '';
      },
    },
    $trs: {
      passwordLabel: 'Password',
    },
  };

</script>


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@MisRob
Copy link
Member

MisRob commented Feb 17, 2026

Hi @vtushar06, thanks! Before we assign a maintainer, I will invite the community review.

@MisRob
Copy link
Member

MisRob commented Feb 17, 2026

📢 ✨ Community Review guidance for both authors and reviewers.

@vtushar06
Copy link
Contributor Author

Yeah sure @MisRob, I would love to get engage with our fellow contributors over this.

@abhiraj75
Copy link
Contributor

abhiraj75 commented Feb 21, 2026

Hey @vtushar06 @MisRob , are there existing tests for the Create page that would cover the new StudioEmailField, StudioPasswordField, and validation logic? Or would new tests be needed as mentioned in the issue's acceptance criteria?
Happy to help if needed!

@MisRob
Copy link
Member

MisRob commented Feb 23, 2026

Hi @abhiraj75, thanks! The page itself was covered with VTL tests #5633 beforehand which I mentioned to @vtushar06.

However still, having few focused tests for the new StudioEmailField and StudioPasswordField would be helpful for sure ~ they are important components that will be re-used in many places. Such tests should cover their most important logic. Then in tests for specific pages, we won't need to re-implement test for every detail, but only test page-specific logic.

@vtushar06
Copy link
Contributor Author

thanks @MisRob, I will proceed my upcoming changes including tests for StudioEmailField and StudioPasswordField

Copy link
Member

@LianaHarris360 LianaHarris360 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your work on this @vtushar06! The newly created components are clean, focused, and correctly match the use case of the original components used. I had a few questions & notes on the changes within Create.vue that need to be resolved.

break;
}
},
validateForm() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed this manual validateField() / validateForm() system is used to replace the Vuetify form validation. Was there any particular reason to not use generateFormMixin for the validation instead?

// We need to check the "acceptedAgreement" here explicitly because it is not a
// Vuetify form field and does not trigger the form validation.
if (this.$refs.form.validate() && this.acceptedAgreement) {
if (this.validateForm() && this.acceptedAgreement) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the previous comment about the acceptedAgreement field not being a Vuetify form field still apply? Is validation triggered if we don't explicitly check "acceptedAgreement" here?

:style="{ color: $themeTokens.error }"
class="field-error"
>
{{ $tr('fieldRequired') }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there an issue with using commonStrings.$tr('fieldRequired') in the template? This file uses two sources for this string, but it would be best to stick to one.

other_use: '',
locations: [],
source: '',
source: {},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an error in the console: [Vue warn]: Invalid prop: custom validator check failed for prop "value" because of how the source field is initialized. Can you update this to be source: { value: '', label: '' }, instead?


.banner {
margin-bottom: 32px;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a visual nitpick, but could we set a width in this banner class so that it matches the width of the text boxes?

Image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a nitpick :)

>
<Banner
:value="!valid"
<StudioBanner
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The banner and textboxes might need to be wrapped in a dedicated <div> element, to be centered within the form container like it was previously.

Original Updated
Image Image

@MisRob
Copy link
Member

MisRob commented Mar 6, 2026

@vtushar06 @LianaHarris360 we recently did these changes to alert banners that for some reason don't take place here. @vtushar06 perhaps you started the branch before the banner update was merged? Can you merge the latest unstable?

const input = screen.getByLabelText(/^password$/i);
await fireEvent.update(input, ' mypassword ');
expect(emitted().input).toBeTruthy();
expect(emitted().input[0][0]).toBe(' mypassword ');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LianaHarris360 unless you already know, would you please be able to double-check on whether not trimming password is indeed expected and how it plays with backend? I would just like to be sure we won't cause any issues with passwords.

Copy link
Member

@MisRob MisRob Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(also, the intention is to eventually use StudioPasswordField not only on Create account page, but we will also use it on Login page and Reset password page ~ even though not in scope of this particular PR)

@MisRob MisRob requested a review from rtibblesbot March 6, 2026 16:24
@MisRob
Copy link
Member

MisRob commented Mar 6, 2026

@vtushar06 we'll also turn on our rtibblesbot review. Its comments are generated by an LLM, and should be evaluated accordingly.

<script>

import { mapActions, mapGetters, mapState } from 'vuex';
import KTextbox from 'kolibri-design-system/lib/KTextbox';
Copy link
Member

@MisRob MisRob Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to import KDS components. They are registered globally.

Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid migration from Vuetify to KDS — the form works correctly, validation is functional, and both LTR/RTL recordings are appreciated.

CI passing. Video recordings verified — layout and interactions look clean.

  • suggestion: Spec calls for generateFormMixin but custom validation used (see inline)
  • suggestion: Inconsistent "field required" strings (see inline)
  • suggestion: Existing create.spec.js skipped tests could be partially re-enabled now (see inline)
  • praise: Good @testing-library/vue test coverage for new wrapper components
  • praise: Both RTL and LTR recordings provided

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Reviewed the pull request diff checking for:

  • Correctness: bugs, edge cases, undocumented behavior, resource leaks, hardcoded values
  • Design: unnecessary complexity, naming, readability, comment accuracy, redundant state
  • Architecture: duplicated concerns, minimal interfaces, composition over inheritance
  • Testing: behavior-based assertions, mocks only at hard boundaries, accurate coverage
  • Completeness: missing dependencies, unupdated usages, i18n, accessibility, security
  • Principles: DRY (same reason to change), SRP, Rule of Three (no premature abstraction)
  • Checked CI status and linked issue acceptance criteria
  • For UI changes: inspected screenshots for layout, visual completeness, and consistency

showOtherField(id) {
return id === uses.OTHER && this.form.uses.includes(id);
},
validateField(field) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Issue #5670 specifies "Replace VForm with <form> and use generateFormMixin for validation," but this PR implements a custom validateField/validateForm approach instead. The custom approach works, but the generateFormMixin (used by ChangePasswordForm, FullNameForm, RequestForm, etc.) would reduce boilerplate and align with the project's established validation pattern.

Was deviating from the spec intentional? If there's a reason generateFormMixin doesn't fit this form (e.g., the complex multi-section structure), it might be worth noting in the PR description.

otherSourcePlaceholder: 'Please describe',

// Privacy policy + terms of service
fieldRequired: 'Field is required',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: This introduces a new translation string 'Field is required', but commonStrings.$tr('fieldRequired') already provides 'This field is required' (from shared/translator.js). The validateField method uses the shared string while the template (lines 133, 147, 185) uses this local one, resulting in two different messages for the same concept.

Consider removing this local fieldRequired entry and using commonStrings.$tr('fieldRequired') consistently — or, if the different wording is intentional, align them.

const input = screen.getByLabelText(/email address/i);
await fireEvent.update(input, ' test@example.com ');
expect(emitted().input).toBeTruthy();
expect(emitted().input[0][0]).toBe('test@example.com');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Good test coverage — rendering, input handling (trimming), blur events, and error display are all covered using @testing-library/vue as the acceptance criteria require. The parallel StudioPasswordField tests similarly cover the raw (untrimmed) password input behavior, which is an important distinction.

:rules="sourceRules"
class="mt-2"
<h2 class="section-header">{{ $tr('sourceLabel') }}*</h2>
<KSelect

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Clean use of KSelect with v-model binding and placeholder — simpler than the previous sourceValue computed + @change handler pattern.

@learningequality learningequality deleted a comment from rtibblesbot Mar 6, 2026
Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean Vuetify-to-KDS migration. CI passing. Video recordings verified from prior review.

0 prior finding(s) resolved.

Prior finding status:

  • UNADDRESSED: RTL margin-left should use logical properties (see inline)
  • UNADDRESSED: Inconsistent "field required" strings between template and validateField (see inline)
  • UNADDRESSED: Skipped tests in create.spec.js (lines 120, 147) cite Vuetify as the blocker — now that KDS components replace Vuetify on this page, those tests should be un-skippable. Consider re-enabling them in this PR.
  • ACKNOWLEDGED: Custom validateField/validateForm instead of generateFormMixin — understood as a design choice for this complex multi-section form.

Findings:

  • suggestion: RTL logical properties (see inline)
  • suggestion: Inconsistent required-field messages (see inline)
  • suggestion: Re-enable skipped create.spec.js tests (noted above)
  • praise: Defensive source value extraction in clean() (see inline)

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

.conditional-field {
margin-top: 8px;
margin-bottom: 8px;
margin-left: 32px;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (UNADDRESSED from prior review): margin-left doesn't flip in RTL layouts. Use margin-inline-start instead.

This applies to all new margin-left declarations in this file:

  • .conditional-field (line 734)
  • .conditional-field-textarea (line 740)
  • .policy-error (line 758)
  • .span-spacing (line 764)
  • .span-spacing span (line 767)
  • .contact-message (line 774)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's ignore this comment. We use RTLCSS https://kolibri-dev.readthedocs.io/en/develop/i18n.html#text-alignment that will automatically change margin-left to margin-right.

// Privacy policy + terms of service
fieldRequired: 'Field is required',
viewToSLink: 'View Terms of Service',
ToSRequiredMessage: 'Please accept our terms of service and policy',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (UNADDRESSED from prior review): This local string 'Field is required' is used by the template (lines 133, 147, 185) for usage/location/source validation, while commonStrings.$tr('fieldRequired') — which produces 'This field is required' — is used by validateField() for text input validation (lines 488–537). Users see two different messages for the same concept.

Consider removing this local entry and using commonStrings.$tr('fieldRequired') consistently, or updating the template to reference the shared string.

// Trim text fields
if (key === 'source') {
if (cleanedData[key] === sources.ORGANIZATION) {
const sourceValue =

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Good defensive handling of the source value — gracefully accommodates both the KSelect object format ({value, label}) and plain strings, preventing runtime errors during the migration.

@MisRob
Copy link
Member

MisRob commented Mar 6, 2026

Sorry I think the bot got wild here because at first I removed one if its comments around RTL :) Still experimenting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Remove Vuetify from Studio] 'Create an account' page

6 participants